package be.ac.ua.customer;

import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.util.UUID;
import java.util.Vector;

import distrcomp.Address;

import net.jini.config.ConfigurationException;
import net.jini.core.entry.Entry;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.Transaction.Created;
import net.jini.core.transaction.TransactionException;
import net.jini.core.transaction.TransactionFactory;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lookup.entry.Name;
import net.jini.space.JavaSpace;
import be.ac.ua.codebase.BankInterface;
import be.ac.ua.codebase.Bidding;
import be.ac.ua.codebase.BiddingEvent;
import be.ac.ua.codebase.CustomerInterface;
import be.ac.ua.codebase.JoinRequest;
import be.ac.ua.codebase.Product;
import be.ac.ua.codebase.StoreConnectorInterface;
import be.ac.ua.codebase.WinningEvent;
import be.ac.ua.customer.gui.GuiInterface;

public class CustomerImplementation implements CustomerInterface, DiscoveryListener {
	private Vector<ServiceRegistrar> fRegistrars = new Vector<ServiceRegistrar>();
	private StoreConnectorInterface fStoreConnector = null;
	private CustomerInterface proxy = null;
	private String firstName;
	private String lastName;
	private Address address;
	private BankInterface bank = null;
	private UUID bankAccountID = null;
	private UUID bankID = null;
	private Vector<String> fNotifications = new Vector<String>();
	private GuiInterface fGui = null;

	public CustomerImplementation(String firstName, String lastName, Address address) throws IOException {
		this.firstName = firstName;
		this.lastName = lastName;
		this.address = address;
		
		//Enable lookup discovery
		String[] groups = new String[] {"NDFLEXTRADE"};
        LookupDiscoveryManager lookupDiscovery = new LookupDiscoveryManager(groups, null, this);
	}
	
	public void setGui(GuiInterface gui) {
		fGui = gui;
		pushNotification("Notifications enabled.");
	}
	
	private void pushNotification(String notification) {
		if (fGui != null)
			fGui.pushNotification(notification);
	}
	
	public CustomerInterface export() throws ExportException {
		Exporter exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0), new BasicILFactory());
		this.proxy = (CustomerInterface) exporter.export(this);
		return this.proxy;
	}
	
	public UUID getBankAccountID() {
		return this.bankAccountID;
	}
	
	public Vector<String> getNotifications() {
		Vector<String> tmp = fNotifications;
		fNotifications.removeAllElements();
		return tmp;
	}
	
	public void registerWithBank() {
       	try {
       		if (this.bank == null) {
       			getBank();
       		}
       		this.bank = this.getBank();
			this.bankAccountID = this.bank.register(this.firstName, this.lastName, this.address.getPostalCode());
		} catch (Exception e) {
			System.out.println("[CUSTOMER] Failed to register with a bank");
			fNotifications.add("Failed to register with a bank");
			pushNotification("Failed to register with a bank");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}

	public BankInterface getBank() throws RemoteException {
		if(this.bank == null) {
			BankInterface bankInterface = null; 
			Entry[] attributes = new Entry[] {new Name("Bank")}; 
			Class[] classes = new Class[] {BankInterface.class};
	
			for (int i = 0; i < fRegistrars.size(); i++) {
				BankInterface tmp = (BankInterface) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
				if(tmp != null) {
					this.bank = tmp;
					this.bankID = UUID.fromString(this.bank.getName());
					break;
				}
			}
		}
		return this.bank;
	}
	
	public JavaSpace getSpace() throws RemoteException {
		Class[] classes = new Class[] {JavaSpace.class};
		JavaSpace javaSpace = null;

		for (int i = 0; i < fRegistrars.size(); i++) {
			JavaSpace tmp = (JavaSpace) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
			if (tmp != null) {
				javaSpace = tmp;
			}
		}
		return javaSpace;
	}
	
	public TransactionManager getTransactionManager() throws RemoteException {
		TransactionManager transactionManager = null;
		
		Entry[] attributes = new Entry[1];
		attributes[0] = new Name ("Transaction Manager");
		Class[] classes = new Class[] {TransactionManager.class};
		
		for (int i = 0; i < fRegistrars.size(); i++) {			
			TransactionManager tmp = (TransactionManager) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
			if (tmp != null) {
				transactionManager = tmp;
			}
		}
		
		if (transactionManager == null)
			System.out.println("[ERROR] - TransactionManager not found!");
		
		return transactionManager;
	}
	
	public StoreConnectorInterface getStoreConnector() throws RemoteException {
		if (fStoreConnector == null) {
			Entry[] attributes = new Entry[] {new Name("StoreConnector")};
			Class[] classes = new Class[] {StoreConnectorInterface.class};

			for (int i = 0; i < fRegistrars.size(); i++) {
				fStoreConnector = (StoreConnectorInterface) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
				if (fStoreConnector != null) {
					break;
				}
			}
		}
		
		if (fStoreConnector == null) {
			System.out.println("NO STORE CONNECTOR FOUND");
		}

		return fStoreConnector;
	}
	
	public void discarded(DiscoveryEvent evt) {
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.remove(registrars[n]);
		}		
	}

	public void discovered(DiscoveryEvent evt) { 
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.add(registrars[n]);
		}
	}
	
	public void joinBidding(UUID productID) throws RemoteException, TransactionException, LeaseDeniedException {
		//Get JavaSpace and Transaction Manager
		JavaSpace space = this.getSpace();
		TransactionManager transManager = this.getTransactionManager();
		
		//Initialize transaction
		Created created = TransactionFactory.create(transManager, 5000);
		Transaction trans = created.transaction;
		
		//Create and write entry
		JoinRequest entry = new JoinRequest(productID, this.proxy, this.firstName + " " + this.lastName);
		space.write(entry, trans, 24*60*60);

		trans.commit();
	}
	
	public void placeBidding(UUID productID, double amount) throws RemoteException, TransactionException, LeaseDeniedException {
		//Get JavaSpace and Transaction Manager
		JavaSpace space = this.getSpace();
		TransactionManager transManager = this.getTransactionManager();
		
		//Initialize transaction
		Created created = TransactionFactory.create(transManager, 5000);
		Transaction trans = created.transaction;
		
		//Create and write entry
		Double amountNP = new Double(amount);
		Bidding entry = new Bidding(productID, this.proxy, this.bankAccountID, this.bankID, amountNP, this.address);
		space.write(entry, trans, 24*60*60);

		trans.commit();
	}
	
	public void offerProduct(Product product) throws RemoteException, LeaseDeniedException, TransactionException {
		StoreConnectorInterface storeConnector = this.getStoreConnector();
		storeConnector.addProduct(product);
	}
	
	
	public void offerProduct(Product product, Long duration, Double minAmount, Double maxAmount, String auctionType) throws RemoteException, LeaseDeniedException, TransactionException, ClassNotFoundException {
		offerProduct(product, duration, minAmount, maxAmount, Class.forName(auctionType));
	}
	
	public void offerProduct(Product product, Long duration, Double minAmount, Double maxAmount, Class auctionType) throws RemoteException, LeaseDeniedException, TransactionException {
		StoreConnectorInterface storeConnector = this.getStoreConnector();
		if((storeConnector != null) && (this.proxy != null) && (this.bankAccountID != null) && (this.bank != null)) {
			storeConnector.addProduct(product, duration, minAmount, maxAmount, auctionType, this.proxy, this.bankAccountID, this.bankID);
		} else {
			if (this.proxy == null) {
				System.out.println("proxy");
			} else if (this.bank == null) {
				System.out.println("bank");
			} else if (this.bankAccountID == null) {
				System.out.println("bankaccountid");
			}
			System.out.println("[CUSTOMER] Offering product failed");
			fNotifications.add("Product offer failed");
			pushNotification("Product offer failed");
		}
	}	

	public void notify(RemoteEvent event) throws UnknownEventException, RemoteException {
		if(event instanceof BiddingEvent) {
			BiddingEvent biddingEvent = (BiddingEvent) event;
			System.out.println("[CUSTOMER] Received notification: " + biddingEvent.getMsg());
			fNotifications.add("Received notification: " + biddingEvent.getMsg());
			pushNotification("Received notification: " + biddingEvent.getMsg());
		} else if(event instanceof WinningEvent) {
			WinningEvent winningEvent = (WinningEvent) event;
			System.out.println("[CUSTOMER] Received notification: " + winningEvent.getMsg());
			fNotifications.add("Received notification: " + winningEvent.getMsg());
			pushNotification("Received notification: " + winningEvent.getMsg());
		}
	}
}
